package com.hexacta.hrs.domain.employee.history;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.vectrics.common.db.AbstractRecordObject;
import org.vectrics.common.util.DateUtil;
import org.vectrics.employee.Employee;

/**
 * Historial de condiciones de trabajo del empleado.
 * 
 * @author jallik
 */
public class EmploymentConditionHistory extends AbstractRecordObject {

    private Set<CareerRole> careerRoles = new HashSet<CareerRole>();

    private Set<MentorAssignment> mentorAssignments = new HashSet<MentorAssignment>();

    private Set<AgreementCondition> agreementConditions = new HashSet<AgreementCondition>();

    private Set<AssignabilityStatus> assignabilityStatuses = new HashSet<AssignabilityStatus>();

    // TODO unificar paquetes con Employee, y bajar la visibilidad de este
    // constructor.
    public EmploymentConditionHistory() {
        setCareerRoles(new HashSet<CareerRole>());
        setMentorAssignments(new HashSet<MentorAssignment>());
        setAgreementConditions(new HashSet<AgreementCondition>());
        setAssignabilityStatuses(new HashSet<AssignabilityStatus>());
    }

    public Set<CareerRole> getCareerRoles() {
        return careerRoles;
    }

    public void setCareerRoles(final Set<CareerRole> careerRoles) {
        this.careerRoles = careerRoles;
    }

    public Set<MentorAssignment> getMentorAssignments() {
        return mentorAssignments;
    }

    public void setMentorAssignments(final Set<MentorAssignment> mentorAssignments) {
        this.mentorAssignments = mentorAssignments;
    }

    public Set<AgreementCondition> getAgreementConditions() {
        return agreementConditions;
    }

    public void setAgreementConditions(final Set<AgreementCondition> agreementConditions) {
        this.agreementConditions = agreementConditions;
    }

    public Set<AssignabilityStatus> getAssignabilityStatuses() {
        return assignabilityStatuses;
    }

    public void setAssignabilityStatuses(final Set<AssignabilityStatus> assignabilityStatuses) {
        this.assignabilityStatuses = assignabilityStatuses;
    }

    public void addMentorAssignment(final Employee mentor, final Date effectiveDate) {

        Date roundedEffectiveDate = DateUtil.roundToDay(effectiveDate);

        if (CollectionUtils.find(mentorAssignments, new BeanPropertyValueEqualsPredicate("effectiveDate",
                roundedEffectiveDate)) != null)
            throw new RuntimeException("Mentor already assigned to the specified date");

        MentorAssignment newMentorAssignement = new MentorAssignment(mentor, roundedEffectiveDate);
        getMentorAssignments().add(newMentorAssignement);

    }

    public Employee getCurrentMentor() {
        if (getCurrentMentorAssignments() != null)
            return getCurrentMentorAssignments().getMentor();
        return null;
    }

    /**
     * retorna la fecha m�s reciente del historial. Si no hay nada en el
     * historual retorna null.
     */
    public Date getLastDate() {
        Date date = new Date(0000 - 01 - 01);
        for (AgreementCondition agreementCondition : agreementConditions) {
            if (agreementCondition.getEffectiveDate().after(date)) {
                date = agreementCondition.getEffectiveDate();
            }
        }
        for (CareerRole careerRole : careerRoles) {
            if (careerRole.getEffectiveDate().after(date)) {
                date = careerRole.getEffectiveDate();
            }
        }
        for (MentorAssignment mentorAssignment : mentorAssignments) {
            if (mentorAssignment.getEffectiveDate().after(date)) {
                date = mentorAssignment.getEffectiveDate();
            }
        }
        for (AssignabilityStatus status : assignabilityStatuses) {
            if (status.getEffectiveDate().after(date)) {
                date = status.getEffectiveDate();
            }
        }
        if (date.equals(new Date(0000 - 01 - 01)))
            return null;
        return date;
    }

    public CareerRole getCurrentCareerRoles() {
        Set<CareerRole> careerRoles = getCareerRoles();
        if (careerRoles.isEmpty())
            return null;

        CareerRole currentCareerRole = Collections.max(careerRoles, new Comparator<EffectiveCondition>() {
            @Override
            public int compare(final EffectiveCondition o1, final EffectiveCondition o2) {
                if (o1.getEffectiveDate() != null && o2.getEffectiveDate() != null)
                    return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
                else
                    return 0;
            }

        });
        return currentCareerRole;
    }

    public MentorAssignment getCurrentMentorAssignments() {
        Set<MentorAssignment> mentorAssignments = getMentorAssignments();
        if (mentorAssignments.isEmpty())
            return null;

        MentorAssignment currentMentorAssignment = Collections.max(mentorAssignments,
                new Comparator<EffectiveCondition>() {
                    @Override
                    public int compare(final EffectiveCondition o1, final EffectiveCondition o2) {
                        if (o1.getEffectiveDate() != null && o2.getEffectiveDate() != null)
                            return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
                        else
                            return 0;
                    }

                });
        return currentMentorAssignment;
    }

    public AgreementCondition getCurrentAgreementConditions() {
        Set<AgreementCondition> agreementConditions = getAgreementConditions();
        if (agreementConditions.isEmpty())
            return null;

        AgreementCondition currentAgreementCondition = Collections.max(agreementConditions,
                new Comparator<EffectiveCondition>() {
                    @Override
                    public int compare(final EffectiveCondition o1, final EffectiveCondition o2) {
                        if (o1.getEffectiveDate() != null && o2.getEffectiveDate() != null)
                            return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
                        else
                            return 0;
                    }

                });
        return currentAgreementCondition;
    }

    public AssignabilityStatus getCurrentAssignabilityStatus() {
        Set<AssignabilityStatus> assignabilityStatus = getAssignabilityStatuses();
        if (assignabilityStatus.isEmpty())
            return null;

        AssignabilityStatus currentAssignabilityStatus = Collections.max(assignabilityStatus,
                new Comparator<EffectiveCondition>() {
                    @Override
                    public int compare(final EffectiveCondition o1, final EffectiveCondition o2) {
                        if (o1.getEffectiveDate() != null && o2.getEffectiveDate() != null)
                            return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
                        else
                            return 0;
                    }

                });
        return currentAssignabilityStatus;
    }

    public Boolean getCurrentAssignable() {
        if (getCurrentAssignabilityStatus() != null)
            return getCurrentAssignabilityStatus().isAvailable();
        return null;
    }

    /**
     * Recupera el Career Role del dia especificado por parametro.
     * 
     * @param date
     * @return
     */
    public CareerRole getCareerRole(final Date date) {
        Set<CareerRole> careerRoles = new HashSet<CareerRole>(getCareerRoles());

        if (careerRoles.isEmpty())
            return null;

        CollectionUtils.filter(careerRoles, new Predicate() {

            @Override
            public boolean evaluate(final Object object) {
                CareerRole careerRole = (CareerRole) object;
                return new Long(careerRole.getEffectiveDate().getTime()).equals(date.getTime());
            }
        });

        if (careerRoles.isEmpty())
            return null;

        return careerRoles.iterator().next();
    }

    /**
     * Recupera el Mentor Assignment del dia especificado por parametro.
     * 
     * @param date
     * @return
     */
    public MentorAssignment getMentorAssignment(final Date date) {
        Set<MentorAssignment> mentorAssignments = new HashSet<MentorAssignment>(getMentorAssignments());

        if (mentorAssignments.isEmpty())
            return null;

        CollectionUtils.filter(mentorAssignments, new Predicate() {

            @Override
            public boolean evaluate(final Object object) {
                MentorAssignment careerRole = (MentorAssignment) object;
                return new Long(careerRole.getEffectiveDate().getTime()).equals(date.getTime());
            }
        });

        if (mentorAssignments.isEmpty())
            return null;

        return mentorAssignments.iterator().next();
    }

    /**
     * Recupera el Agreement Condition del dia especificado por parametro.
     * 
     * @param date
     * @return
     */
    public AgreementCondition getAgreementCondition(final Date date) {
        Set<AgreementCondition> agreementCondition = new HashSet<AgreementCondition>(getAgreementConditions());

        if (agreementCondition.isEmpty())
            return null;

        CollectionUtils.filter(agreementCondition, new Predicate() {

            @Override
            public boolean evaluate(final Object object) {
                AgreementCondition careerRole = (AgreementCondition) object;
                return new Long(careerRole.getEffectiveDate().getTime()).equals(date.getTime());
            }
        });

        if (agreementCondition.isEmpty())
            return null;

        return agreementCondition.iterator().next();
    }

    /**
     * Recupera el Agreement Condition del dia especificado por parametro.
     * 
     * @param date
     * @return
     */
    public AssignabilityStatus getAssignabilityStatus(final Date date) {
        Set<AssignabilityStatus> assignabilityStatus = new HashSet<AssignabilityStatus>(getAssignabilityStatuses());

        if (assignabilityStatus.isEmpty())
            return null;

        CollectionUtils.filter(assignabilityStatus, new Predicate() {

            @Override
            public boolean evaluate(final Object object) {
                AssignabilityStatus assigStatus = (AssignabilityStatus) object;
                return new Long(assigStatus.getEffectiveDate().getTime()).equals(date.getTime());
            }
        });

        if (assignabilityStatus.isEmpty())
            return null;

        return assignabilityStatus.iterator().next();
    }

    public boolean getAssignable(final Date date) {
        TreeSet<AssignabilityStatus> assignabilityStatus = new TreeSet<AssignabilityStatus>(getAssignabilityStatuses());

        if (assignabilityStatus.isEmpty())
            return false;

        AssignabilityStatus falseStat = new AssignabilityStatus();
        falseStat.setEffectiveDate(date);
        AssignabilityStatus assigStat = assignabilityStatus.floor(falseStat);

        return assigStat.isAvailable();
    }

    /**
     * Indica si es el ultimo registro dle historial.
     * 
     * @return
     */
    public boolean isLast() {
        Map<Date, CareerRole> careerRoles = new HashMap<Date, CareerRole>();
        for (CareerRole cr : getCareerRoles()) {
            careerRoles.put(cr.getEffectiveDate(), cr);
        }

        Map<Date, MentorAssignment> mentorAssignments = new HashMap<Date, MentorAssignment>();
        for (MentorAssignment ma : getMentorAssignments()) {
            mentorAssignments.put(ma.getEffectiveDate(), ma);
        }

        Map<Date, AgreementCondition> agreementConditions = new HashMap<Date, AgreementCondition>();
        for (AgreementCondition ac : getAgreementConditions()) {
            agreementConditions.put(ac.getEffectiveDate(), ac);
        }

        Map<Date, AssignabilityStatus> assigStatus = new HashMap<Date, AssignabilityStatus>();
        for (AssignabilityStatus as : getAssignabilityStatuses()) {
            assigStatus.put(as.getEffectiveDate(), as);
        }

        Set<Date> dates = new TreeSet<Date>();
        dates.addAll(careerRoles.keySet());
        dates.addAll(mentorAssignments.keySet());
        dates.addAll(agreementConditions.keySet());
        dates.addAll(assigStatus.keySet());

        return dates.size() <= 1;
    }

    /**
     * Remueve los elementos del historial que pertenecen a la fecha pasada por
     * parametro.
     * 
     * @param date
     */
    public void remove(final Date date) {
        CareerRole careerRole = getCareerRole(date);
        if (careerRole != null) {
            remove(careerRole);
        }

        MentorAssignment mentorAssignment = getMentorAssignment(date);
        if (mentorAssignment != null) {
            remove(mentorAssignment);
        }

        AgreementCondition agreementCondition = getAgreementCondition(date);
        if (agreementCondition != null) {
            remove(agreementCondition);
        }

        AssignabilityStatus assignabilityStatus = getAssignabilityStatus(date);
        if (assignabilityStatus != null) {
            remove(assignabilityStatus);
        }
    }

    private void remove(final CareerRole careerRole) {
        Iterator<CareerRole> itx = careerRoles.iterator();
        while (itx.hasNext()) {
            if (itx.next().getId().equals(careerRole.getId())) {
                itx.remove();
                break;
            }
        }
    }

    private void remove(final MentorAssignment mentorAssignment) {
        Iterator<MentorAssignment> itx = mentorAssignments.iterator();
        while (itx.hasNext()) {
            if (itx.next().getId().equals(mentorAssignment.getId())) {
                itx.remove();
                break;
            }
        }
    }

    private void remove(final AgreementCondition agreementCondition) {
        Iterator<AgreementCondition> itx = agreementConditions.iterator();
        while (itx.hasNext()) {
            if (itx.next().getId().equals(agreementCondition.getId())) {
                itx.remove();
                break;
            }
        }
    }

    private void remove(final AssignabilityStatus assignabilityStatus) {
        Iterator<AssignabilityStatus> itx = assignabilityStatuses.iterator();
        while (itx.hasNext()) {
            if (itx.next().getId().equals(assignabilityStatus.getId())) {
                itx.remove();
                break;
            }
        }
    }

    public boolean isExist(final Date date) {
        AgreementCondition agreementCondition = getAgreementCondition(date);
        CareerRole careerRole = getCareerRole(date);
        MentorAssignment mentorAssignment = getMentorAssignment(date);
        AssignabilityStatus assignabilityStatus = getAssignabilityStatus(date);
        return agreementCondition != null || careerRole != null || mentorAssignment != null
                || assignabilityStatus != null;
    }

    public CareerRole getCareerRoleInDate(final Date date) {

        if (getCareerRoles().isEmpty())
            return null;

        List<CareerRole> careerRoles = new ArrayList<CareerRole>(getCareerRoles());

        Collections.sort(careerRoles, new Comparator<CareerRole>() {

            /*
             * compares ordering by date, from earliest to oldest
             */
            @Override
            public int compare(final CareerRole o1, final CareerRole o2) {
                return o1.compareTo(o2);
            }
        });

        try {

            if (careerRoles.isEmpty() || date.before(careerRoles.get(0).getEffectiveDate()))
                return null;

            if (careerRoles.size() == 1)
                return careerRoles.get(0);

            for (int i = 0; i < careerRoles.size(); i++) {
                if (careerRoles.get(i).getEffectiveDate().after(date))
                    return careerRoles.get(i - 1);
            }

            return careerRoles.get(careerRoles.size() - 1);

        } catch (Exception e) {
            // Si existe algun error, retorno el primer rol que tenia, dado que
            // el error puede ser
            // porque se asigno unos dias antes de que el empleado entre.
            return careerRoles.get(0);
        }

    }

    /**
     * @param assignabilityStatus
     */
    public void addAssignabilityStatus(final AssignabilityStatus assignabilityStatus) {
        TreeSet<AssignabilityStatus> ordered = new TreeSet<AssignabilityStatus>(getAssignabilityStatuses());

        AssignabilityStatus previousStat = ordered.floor(assignabilityStatus);

        if (previousStat == null) {
            getAssignabilityStatuses().add(assignabilityStatus);
            return;
        }

        assignabilityStatus.setEndDate(previousStat.getEndDate());
        previousStat.setEndDate(assignabilityStatus.getEffectiveDate());
        getAssignabilityStatuses().add(assignabilityStatus);
    }

}
